
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/pinctrl/consumer.h>

#define API_ENTER() printk("func: %s at line: %d is called\r\n", __FUNCTION__, __LINE__);
#define API_EXIT()  printk("func: %s exit\r\n", __FUNCTION__);


#define FSPWM_MAGIC	'f'
#define DEVICE_COUNT 1

#define FSPWM_START	_IO(FSPWM_MAGIC, 0)
#define FSPWM_STOP	_IO(FSPWM_MAGIC, 1)
#define FSPWM_SET_FREQ	_IOW(FSPWM_MAGIC, 2, unsigned int)


static int major = 256;
static int minor = 7;

struct fspwm_drv{
	unsigned int __iomem *tcfg0;
	unsigned int __iomem *tcfg1;
	unsigned int __iomem *tcon;
	unsigned int __iomem *tcntb0;
	unsigned int __iomem *tcmpb0;
	unsigned int __iomem *tcnto0;
	struct clk* clk;
	unsigned long freq;
	struct pinctrl* pctrl;
	atomic_t available;
	struct cdev cdev;
};

static struct class * pClass = NULL;
struct resource *res;
struct cdev cdev;
struct fspwm_drv *fspwm;

static int fspwm_open(struct inode *inode, struct file *filp)
{
	dev_t devNo;
	int   openMinor = 0;
	
	devNo = inode->i_rdev;
	openMinor = MINOR(devNo);
	return 0;
}

static int fspwm_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static long fspwm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	//struct fspwm_drv *fspwm = filp->private_data;
	unsigned int div;
	printk("cmd:%u,arg:%ld\r\n",cmd,arg);
	if (_IOC_TYPE(cmd) != FSPWM_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case FSPWM_START:
		writel(readl(fspwm->tcon) |0x1,fspwm->tcon);
		break;
	case FSPWM_STOP:
		writel(readl(fspwm->tcon) &(~0x1),fspwm->tcon);
		break;
	case FSPWM_SET_FREQ:
		if(arg>fspwm->freq||arg==0)
			return -ENOTTY;
		div = fspwm->freq/arg - 1;
		writel(div,fspwm->tcntb0);
		writel(div/2,fspwm->tcmpb0);
		writel(readl(fspwm->tcon)|0x2,fspwm->tcon);
		writel(readl(fspwm->tcon)&(~0x2),fspwm->tcon);
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations fspwm_ops = {
	.owner = THIS_MODULE,
	.open = fspwm_open,
	.release = fspwm_release,
	.unlocked_ioctl = fspwm_ioctl,
};

static int pwmdrv_probe(struct platform_device * pdev)
{
	dev_t devNo = 0;
	int ret = 0;
	struct device * pDevice = NULL;
	unsigned int prescaler0;
	API_ENTER();
	//申请设备号
	devNo = MKDEV(major, minor);
	if (major)
	{
		ret = register_chrdev_region(devNo, 1, "fspwm");
	}
	else 
	{
		ret = alloc_chrdev_region(&devNo, minor, 1, "fspwm");
	}
	
	if (ret)
	{
		printk("register char devNo error\r\n");
		goto REGISTER_ERR;
	}
	major = MAJOR(devNo);
	minor = MINOR(devNo);
	printk("register success, major = %d, minor = %d\r\n", major, minor);

	fspwm = kzalloc(sizeof(struct fspwm_drv),GFP_KERNEL);
	if(!fspwm){
			ret = -ENOMEM;
			goto MEM_ERR;
	}
	platform_set_drvdata(pdev,fspwm);

	//初始化cdev -- cdev_init(&cdev, &fops);
	cdev_init(&fspwm->cdev, &fspwm_ops);
	cdev.owner = THIS_MODULE;
	//加载cdev
	ret = cdev_add(&fspwm->cdev, devNo, 1);
	if (ret)
	{
		printk("cdev add error\r\n");
		goto CDEV_ADD_ERR;
	}
	printk("cdev add  ok\r\n");
	//创建class
	pClass = class_create(THIS_MODULE, "pwmClass");
	if (IS_ERR(pClass))
	{
		printk("class create error\r\n");
		ret = -EFAULT;
		goto CLASS_CREATE_ERR;
	}
	printk("class create ok\r\n");
	//创建设备名
	pDevice = device_create(pClass, NULL, devNo, NULL, "pwm");
	if (IS_ERR(pDevice)) 
	{
		printk("device create error\r\n");
		ret = -EFAULT;
		goto DEVICE_CREATE_ERR;
	}	
	printk("device create ok\r\n");

	//获取资源	
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if(!res){
		ret = -EBUSY;
		goto RES_ERR;
	}
	printk("get resource ok\r\n");

	//物理地址映射虚拟地址
	fspwm->tcfg0 = ioremap(res->start,resource_size(res));
	if(!fspwm->tcfg0){
			ret = -EBUSY;
			goto MAP_ERR;
	}
	fspwm->tcfg1 = fspwm->tcfg0 + 1;
	fspwm->tcon = fspwm->tcfg0 + 2;
	fspwm->tcntb0 = fspwm->tcfg0 + 3;
	fspwm->tcmpb0 = fspwm->tcfg0 + 4;
	fspwm->tcnto0 = fspwm->tcfg0 + 5;
	printk("ioremap ok\r\n");

	fspwm->clk = clk_get(&pdev->dev,"timers");
	if(IS_ERR(fspwm->clk)){
			ret = PTR_ERR(fspwm->clk);
			goto GET_CLK_ERR;
	}

	ret = clk_prepare_enable(fspwm->clk);
	if(ret < 0)	{
				goto ENABLE_CLK_ERR;
	}
	fspwm->freq = clk_get_rate(fspwm->clk);	
	
	prescaler0 = readl(fspwm->tcfg0)&0xff;
	writel((readl(fspwm->tcfg1)&(~0xf) )| 0x4,fspwm->tcfg1);  //1/16
	fspwm->freq /= (prescaler0+1)*16; //3125000
	writel((readl(fspwm->tcon)&(~0xf)) | 0x8,fspwm->tcon);  //auto-reload

	fspwm->pctrl = devm_pinctrl_get_select_default(&pdev->dev);
	printk("declear ok\r\n");
	API_EXIT();
	return 0;

ENABLE_CLK_ERR:
	clk_put(fspwm->clk);
GET_CLK_ERR:
	iounmap(fspwm->tcfg0);
MAP_ERR:
	res = NULL;
RES_ERR:
	device_destroy(pClass,devNo);
DEVICE_CREATE_ERR:
	class_destroy(pClass);
CLASS_CREATE_ERR:
	cdev_del(&cdev);
CDEV_ADD_ERR:
	unregister_chrdev_region(devNo, DEVICE_COUNT);	
REGISTER_ERR:
MEM_ERR:
	return ret;

}

static int pwmdrv_remove(struct platform_device *pdev)
{

	dev_t devNo;
	struct fspwm_drv *fspwm = platform_get_drvdata(pdev);

	devNo = MKDEV(major, minor);

	clk_put(fspwm->clk);
	//解除--物理地址映射虚拟地址
	iounmap(fspwm->tcfg0);
	//删除class
	device_destroy(pClass,devNo);
	class_destroy(pClass);
	//删除cdev
	cdev_del(&cdev);
	//删除设备号
	unregister_chrdev_region(devNo, DEVICE_COUNT);	
	return 0;
}

//匹配设备树中的key节点
static const struct of_device_id hello_pwm_table[] = {
	{ .compatible = "fs4412,beep" ,},
	{ /* sentinel */ }
};

static struct platform_driver pwm_driver = {
	.probe = pwmdrv_probe,
	.remove = pwmdrv_remove,
	.driver = {
		.name = "fs4412_sqadc_drv",
		.of_match_table = hello_pwm_table,
	},
};

module_platform_driver(pwm_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("this is platform + key + fasync  for linux 3.14 for FS4412");
MODULE_AUTHOR("Leo 《98361462@qq.com》");
MODULE_VERSION("1.0");
